﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using MongoDB.Bson;
using Cherry.Agile.Distributed.Queue;

namespace Cherry.Agile.Distributed.Queue.Impl
{
    public class MongoDbQueueRepository : IQueueRepository
    {
        MongoDB.Driver.MongoServer server;
        MongoDatabase db;
        const string dbName = "Queue";
        const string indexCollectionName = "qindex";

        MongoCollection indexer;
        //volatile int _length = 1000;
        //volatile int _wrIndex = 0;
        //volatile int _rdIndex = 0;

        public MongoDbQueueRepository(System.Collections.Specialized.NameValueCollection @params)
        {
            try
            {
                server = MongoDB.Driver.MongoServer.Create("mongodb://" + @params["server"]);
            }
            catch (Exception ex)
            {
                throw new ArgumentException("connect mongo server error.", ex);
            }


            db = server.GetDatabase(dbName);
            indexer = db.GetCollection(indexCollectionName);
            if (indexer.GetIndexes().Count() <= 1)
                indexer.CreateIndex("name");
        }

        public string Get(string name, int? index,out int newIndex)
        {
            var jobs = db.GetCollection(name);
            BsonDocument item;
            newIndex = -1;
            if (index.HasValue)
            {
                item = jobs.Find(Query.Null).Skip(index.Value).FirstOrDefault();
                if (item != null)
                    return item["s"].AsString;
                else
                    return string.Empty;
            }

            var id = GetIndexDoc(name, true);
            if (id != null)
            {
                index = id["r"].AsInt32;
                item = jobs.Find(Query.Null).Skip(index.Value).FirstOrDefault();
                if (item != null && item["s"].AsInt32 == 0)
                {
                    item["s"] = 1;
                    jobs.Save(item);
                    newIndex = index.Value;
                    return item["d"].AsString;
                }

                if (item == null)
                {
                    var query = Query.And(Query.EQ("m", 1), Query.EQ("name", name));
                    var temp = indexer.FindAndModify(query, SortBy.Null, MongoDB.Driver.Builders.Update.Set("m", 0).Inc("r", -1), true);
                }
            }
            return string.Empty;
        }

        public int Set(string name, string data)
        {
            var jobs = db.GetCollection(name);
            BsonDocument item;
            var indexer = GetIndexDoc(name, false);
            var index = indexer["w"].AsInt32 % 1000;
            item = jobs.Find(Query.Null).Skip(index).FirstOrDefault();
            if (item == null)
            {
                item = new BsonDocument { { "d", data }, { "s", 0 } };
                jobs.Insert(item);
            }
            else
            {
                item["d"] = data;
                jobs.Save(item);
            }
            return index;
        }

        public void Max(int length)
        {

        }

        #region private methods
        //UpdateBuilder rupdate = MongoDB.Driver.Builders.Update.Inc("r", 1);
        //UpdateBuilder wupdate = MongoDB.Driver.Builders.Update.Inc("w", 1);

        private BsonDocument GetIndexDoc(string queueName, bool get, int val = 1)
        {
            string field = get ? "r" : "w";

            var query = get ? Query.And(Query.EQ("m", 1), Query.EQ("name", queueName)) : Query.EQ("name", queueName);
            var update = MongoDB.Driver.Builders.Update.Inc(field, val);
            if (!get)
                update.Set("m", 1);
            var result = indexer.FindAndModify(
                query,
                SortBy.Null,
                update,
                true
            );
            var item = result.ModifiedDocument;
            if (item == null && !get)
            {
                var temp = new BsonDocument { { "name", queueName }, { "r", get ? 0 : -1 }, { "w", get ? -1 : 0 }, { "m", 1 } };
                indexer.Insert(temp);
                return temp;
            }

            return item;
        }

        #endregion

    }
}
